<?php

class CliMode
{

	const ID_SETTING = 1;
	const ID_COMMAND = 2;
	const ID_OPERATOR = 3;
	const ID_FUNCTION = 4;
	const ID_CONSTANT = 5;

	private $modeHandler;
	private $identifiers;
	private $calls;
	private $operators;
	private $settings;
	private $constants;

	public function __construct ($mode)
	{
		$this->identifiers = array();

		$this->calls = array(
			self::ID_COMMAND => array(),
			self::ID_OPERATOR => array(),
			self::ID_FUNCTION => array(),
		);

		$this->operators = array(
			ExpOperator::OP_PREFIX => array(),
			ExpOperator::OP_OTHER => array(),
		);

		$classname = ucfirst(strtolower($mode)) . 'ModeHandler';

		// Load the mode handler
		require CLIDIR . "/libraries/$mode/$classname.class.php";
		$this->modeHandler = new $classname();

		if (!($this->modeHandler instanceof ModeHandler))
		{
			throw new CliModeException(
				"Given mode handler does not implement ModeHandler");
		}

		$this->loadLibrary('internal');
		$this->loadLibrary($mode);
	}

	public function loadLibrary ($key)
	{
		require CLIDIR . "/libraries/$key/library.php";
		$name = ucfirst(strtolower($key));

		// Load all the libraries defined in the file
		foreach ($libraries as $lib)
		{
			require CLIDIR . "/libraries/$key/$lib.lib.php";
			$class = $name . 'Lib' . $lib;

			$obj = new $class();

			if (!($obj instanceof CliLibrary))
			{
				throw new CliModeException(
					"Given library '$lib' does not implement CliLibrary");
			}

			$obj->addLibrary($this);
		}
	}

	public function callExists ($name, $type)
	{
		return isset($this->calls[$type][$name]);
	}

	public function getCall ($name, $type)
	{
		if (!$this->callExists($name, $type))
		{
			throw new CliIdentifierException(
				"Unknown call '$name' of type '$type'");
		}

		return $this->calls[$type][$name];
	}

	public function operatorExists ($op, $group)
	{
		return isset($this->operators[$group][$op]);
	}

	public function getOperator ($op, $group)
	{
		if (!isset($this->operators[$group][$op]))
		{
			throw new CliIdentifierException(
				"Unknown operator '$op' in group '$group'");
		}

		return $this->operators[$group][$op];
	}

	public function getOperators ($group)
	{
		if (!isset($this->operators[$group]))
		{
			throw new CliIdentifierException(
				"Unknown operator group '$group'");
		}

		return array_keys($this->operators[$group]);
	}

	public function getOperatorByName ($name)
	{
		if (!isset($this->identifiers[$name]))
		{
			throw new CliIdentifierException(
				"Unknown identifier '$name'");
		}
		if ($this->identifiers[$name] != self::ID_OPERATOR)
		{
			throw new CliIdentifierException(
				"Identifier '$name' is not an operator");
		}

		// Find the operator with the name among the operator groups
		foreach ($this->operators as $group => $list)
		{
			foreach ($list as $op => $info)
			{
				if ($info['name'] == $name)
				{
					return new ExpOperator($op, $group, $this);
				}
			}
		}

		// If we have an operator with the given ID, we should never get here
		throw new CliRuntimeException(
			"Unable to find operator '$name'");
	}

	public function getIdentifierType ($name)
	{
		return isset($this->identifiers[$name])
			? $this->identifiers[$name] : false;
	}

	public function getIdentifiers ($type = false)
	{
		if ($type === false)
		{
			return array_keys($this->identifiers);
		}
		else
		{
			return array_keys($this->identifiers, $type);
		}
	}

	public function getSetting ($name)
	{
		if (!isset($this->settings[$name]))
		{
			throw new CliArgumentException("Unknown setting '$name'");
		}

		return $this->settings[$name];
	}

	public function getConstant ($name)
	{
		if (!isset($this->constants[$name]))
		{
			throw new CliIdentifierException("Unknown constant '$name'");
		}

		return $this->constants[$name];
	}

	/* ############### Mode handler wrappers ############### */

	public function getInt ($number)
	{
		return $this->modeHandler->getInt($number);
	}

	public function getFloat ($number)
	{
		return $this->modeHandler->getFloat($number);
	}

	public function getModeNumber ($number)
	{
		return $this->modeHandler->getModeNumber($number);
	}

	public function handleResult ($result, CliContext $context)
	{
		return $this->modeHandler->handleResult($result, $context);
	}

	/* ############### Adding identifiers ############### */

	private function addIdentifier ($name, $type)
	{
		if (isset($this->identifiers[$name]))
		{
			throw new CliIdentifierException(
				"Identifier '$name' already exists");
		}

		$this->identifiers[$name] = $type;
	}

	private function addCall ($name, $call, $type)
	{
		$this->addIdentifier($name, $type);
		$this->calls[$type][$name] = $call;
	}

	private function addCalls ($class, array $calls, $type)
	{
		foreach ($calls as $name => $call)
		{
			$this->addCall($name, array($class, $call), $type);
		}
	}

	public function addOperators ($class, array $operators)
	{
		foreach ($operators as $type => $list)
		{
			foreach ($list as $op => $values)
			{
				$group = $type == ExpOperator::TYPE_PREFIX
					? ExpOperator::OP_PREFIX : ExpOperator::OP_OTHER;

				if (isset($this->operators[$group][$op]))
				{
					throw new CliIdentifierException(
						"Operator '$op' already exists");
				}

				$prec = $values[0];
				$call = array($class, $values[1]);
				$name = empty($values[2]) ? $op : $values[2];

				$this->addCall($name, $call, self::ID_OPERATOR);
				$this->operators[$group][$op] = array(
					'type' => $type,
					'prec' => $prec,
					'name' => $name,
				);
			}
		}
	}

	public function addFunctions ($class, array $functions)
	{
		$this->addCalls($class, $functions, self::ID_FUNCTION);
	}

	public function addCommands ($class, array $commands)
	{
		$this->addCalls($class, $commands, self::ID_COMMAND);
	}

	public function addSettings ($class, array $settings)
	{
		foreach ($settings as $name => $value)
		{
			$this->addIdentifier($name, self::ID_SETTING);
			$this->settings[$name] = array(
				'default' => $value,
				'validator' => method_exists($class, "validate_$name")
					? array($class, "validate_$name") : false
			);
		}
	}

	public function addConstants (array $constants)
	{
		foreach ($constants as $name => $value)
		{
			$this->addIdentifier($name, self::ID_CONSTANT);
			$this->constants[$name] = $value;
		}
	}

}

?>